using System;
using System.Text;

namespace Firestarter.DB.Schema.Logic
{
    public interface IRegexBuilder
    {
        IRegexBuilder StartText { get; }
        IRegexBuilder EndText { get; }
        IRegexBuilder StartLine { get; }
        IRegexBuilder EndLine { get; }

        IRegexBuilder Open { get ; }
        IRegexBuilder Or { get; }
        IRegexBuilder Close { get; }

        IRegexBuilder WhiteSpace { get; }
        IRegexBuilder AlphaNumeric { get; }
        IRegexBuilder Any { get; }
        IRegexBuilder Numeric { get; }

        IRegexBuilder ZeroToOne { get; }
        IRegexBuilder ZeroToMany { get; }
        IRegexBuilder OneToMany { get; }

        IRegexBuilder Specific(string text);
        IRegexBuilder Delimited(IRegexBuilder regexBuilder, string delimiter, int minItems, int maxItems);
        IRegexBuilder Repeated(int min, int max);
        IRegexBuilder Alternatives(params IRegexBuilder[] regexBuilders);
        IRegexBuilder Append(IRegexBuilder builder);
    }
    public class RegexBuilder : RegexBuilder<RegexBuilder> { }
    public class RegexBuilder<TRegexBuilder> : IRegexBuilder
        where TRegexBuilder : IRegexBuilder, new()
    {
        readonly StringBuilder _stringBuilder = new StringBuilder();

        public RegexBuilder()
        {
            if (!(this is TRegexBuilder)) throw new InvalidCastException("The RegexBuilder must be declared using recursive Generics.");
        }

        IRegexBuilder IRegexBuilder.StartText { get { return this.StartText; } }
        public TRegexBuilder StartText { get { return AppendAndReturn(@"\A"); } }
        IRegexBuilder IRegexBuilder.EndText { get { return this.EndText; } }
        public TRegexBuilder EndText { get { return AppendAndReturn(@"\Z"); } }

        IRegexBuilder IRegexBuilder.StartLine { get { return this.StartLine; } }
        public TRegexBuilder StartLine { get { return AppendAndReturn(@"^"); } }
        IRegexBuilder IRegexBuilder.EndLine { get { return this.EndLine; } }
        public TRegexBuilder EndLine { get { return AppendAndReturn(@"$"); } }

        IRegexBuilder IRegexBuilder.Open { get { return this.Open; } }
        public TRegexBuilder Open { get { return AppendAndReturn(@"("); } }
        IRegexBuilder IRegexBuilder.Or { get { return this.Or; } }
        public TRegexBuilder Or { get { return AppendAndReturn(@"|"); } }
        IRegexBuilder IRegexBuilder.Close { get { return this.Close; } }
        public TRegexBuilder Close { get { return AppendAndReturn(@")"); } }

        IRegexBuilder IRegexBuilder.WhiteSpace { get { return this.WhiteSpace; } }
        public TRegexBuilder WhiteSpace { get { return AppendAndReturn(@"\s"); } }
        IRegexBuilder IRegexBuilder.AlphaNumeric { get { return this.AlphaNumeric; } }
        public TRegexBuilder AlphaNumeric { get { return AppendAndReturn(@"\w"); } }
        IRegexBuilder IRegexBuilder.Numeric { get { return this.Numeric; } }
        public TRegexBuilder Numeric { get { return AppendAndReturn(@"\d"); } }
        IRegexBuilder IRegexBuilder.Any { get { return this.Any; } }
        public TRegexBuilder Any { get { return AppendAndReturn(@"."); } }

        IRegexBuilder IRegexBuilder.ZeroToOne { get { return this.ZeroToOne; } }
        public TRegexBuilder ZeroToOne { get { return AppendAndReturn(@"?"); } }
        IRegexBuilder IRegexBuilder.ZeroToMany { get { return this.ZeroToMany; } }
        public TRegexBuilder ZeroToMany { get { return AppendAndReturn(@"*"); } }
        IRegexBuilder IRegexBuilder.OneToMany { get { return this.OneToMany; } }
        public TRegexBuilder OneToMany { get { return AppendAndReturn(@"+"); } }

        IRegexBuilder IRegexBuilder.Specific(string text)
        { return this.Specific(text);}
        public TRegexBuilder Specific(string text)
        {
            text = ReplaceWithEscaped(text, @"\");
            text = ReplaceWithEscaped(text, "(");
            text = ReplaceWithEscaped(text, ")");
            text = ReplaceWithEscaped(text, "[");
            text = ReplaceWithEscaped(text, "]");
            text = ReplaceWithEscaped(text, ".");
            text = ReplaceWithEscaped(text, "+");
            text = ReplaceWithEscaped(text, "*");
            return AppendAndReturn(text);
        }

        IRegexBuilder IRegexBuilder.Delimited(IRegexBuilder regexBuilder, string delimiter, int minItems, int maxItems)
        { return this.Delimited(regexBuilder, delimiter, minItems, maxItems);}
        public TRegexBuilder Delimited(IRegexBuilder regexBuilder, string delimiter, int minItems, int maxItems)
        {
            IRegexBuilder thisBuilder = this.Open.Append(regexBuilder).Alternatives(
                null,
                Create().Specific(delimiter).Append(regexBuilder)
                ).Repeated(minItems, maxItems - 1).Close;
            if (minItems == 0 && maxItems > 0) thisBuilder.Repeated(0, 1);
            return ThisToReturn();
        }

        private TRegexBuilder ThisToReturn()
        {
            return (TRegexBuilder)(IRegexBuilder)this;
        }

        IRegexBuilder IRegexBuilder.Repeated(int min, int max)
        { return this.Repeated(min, max); }
        public TRegexBuilder Repeated(int min, int max)
        {
            return AppendAndReturn("{" + min + "," + max + "}");
        }

        IRegexBuilder IRegexBuilder.Append(IRegexBuilder builder)
        { return Append(builder); }
        public TRegexBuilder Append(IRegexBuilder builder)
        {
            return AppendAndReturn(builder);
        }

        IRegexBuilder IRegexBuilder.Alternatives(params IRegexBuilder[] regexBuilders)
        { return this.Alternatives(regexBuilders); }
        public TRegexBuilder Alternatives(params IRegexBuilder[] regexBuilders)
        {
            string[] alternatives = new string[regexBuilders.Length];
            for (int i = 0; i < alternatives.Length; i++)
            {
                alternatives[i] = Convert.ToString(regexBuilders[i]);
            }
            string text = String.Join("|", alternatives);
            text = string.Format("({0})", text);
            return AppendAndReturn(text);
        }

        private string ReplaceWithEscaped(string text, string charText)
        {
            return text.Replace(charText, @"\" + charText);
        }

        private TRegexBuilder AppendAndReturn(IRegexBuilder regexBuilder)
        {
            return AppendAndReturn(Convert.ToString(regexBuilder));
        }

        private TRegexBuilder AppendAndReturn(string text)
        {
            _stringBuilder.Append(text);
            return ThisToReturn();
        }

        public override string ToString()
        {
            return _stringBuilder.ToString();
        }

        public static TRegexBuilder Create()
        {
            return new TRegexBuilder();
        }
    }
}
